/*
 * pwm_sgrt means pwm-signal-generator
 *
 *
 */
#include"i2c-dev.h"
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<error.h>

/* ------------ software dependent ----------- */
#define DEV_NAME "pca9685: "
static int I2CWAIT = 1; /* refer to i2c-dev.h */
static char *i2c_dev_filename = "/dev/i2c-0";
static int i2c_dev_fd = -1;

/* `0' means not succeed to init;
 * `1' means succeed to init;
 * then the other interface can be called */
static int succeed_init_flag = 0;


/* ------------ hardware dependent ----------- */
/* 64 == (100 0000)b, refer to pca9685-manual and sch */
static unsigned char DevSlaveAddr = 64;
static unsigned char regaddr_MODEL1 = 0x00;
static unsigned char regaddr_PRE_SCALE = 0xfe;

int pwm_sgrt_init(void) {
    if(succeed_init_flag) {
        fprintf(stderr, DEV_NAME"pwm-sgrt has already initialized.\n");
        return 0; /* It's ready */
    }

    i2c_dev_fd = open(i2c_dev_filename, O_RDWR);
    if(i2c_dev_fd == -1) {
        fprintf(stderr, DEV_NAME
                "pwm-sgrt does not succeed to init, %s.\n",
                __func__);
        return -1;
    }

    setI2CSlave(i2c_dev_fd, DevSlaveAddr);

    succeed_init_flag = 1;
    return 0;
}

int pwm_sgrt_exit(void) {
    if(!succeed_init_flag) {
        fprintf(stderr, DEV_NAME
                "pwm-sgrt does not succeed to init, %s.\n",
                __func__);
        return -1;
    }

    if(i2c_dev_fd != -1)
        return close(i2c_dev_fd);
}

unsigned char pwm_sgrt_reset(void) {
    /* restart the pca9685 */
    if(!succeed_init_flag) {
        fprintf(stderr, DEV_NAME
                "pwm-sgrt does not succeed to init, %s.\n",
                __func__);
        return -1;
    }
    /* refer to pca9685-manual-7.3.1.1-resart-mode */
    unsigned char val;
    val = I2CReadByteFrom(i2c_dev_fd, regaddr_MODEL1, I2CWAIT);

    /* clear MODEL1_SLEEP-bit to `0', but do not effect the MODEL1_RESTART-bit */
    val = I2CWriteByteTo(i2c_dev_fd, regaddr_MODEL1, 0, I2CWAIT);
    /* Allow time for oscillator to stabilize */

    /* set RESTART-bit to `1' */
#define MODEL1_RESTART (1 << 7)
    val |= MODEL1_RESTART;
	val = I2CWriteByteTo(i2c_dev_fd,  regaddr_MODEL1, val, I2CWAIT);

	return val;
}

unsigned char pwm_sgrt_setfrq(unsigned char out_freq) {
    /* set the output freqence */
    if(!succeed_init_flag) {
        fprintf(stderr, DEV_NAME
                "pwm-sgrt does not succeed to init, %s.\n",
                __func__);
        return -1;
    }
	unsigned char ret,
                  prescalevalue,
                  old_mode1,
                  new_mode1;

    /* Enable to set the PRE_SCALE */
	old_mode1 = I2CReadByteFrom(i2c_dev_fd, regaddr_MODEL1, I2CWAIT);
	new_mode1 = old_mode1 | (1 << 4); /* set the MODEL1_SLEEP-bit to `1' */
	ret = I2CWriteByteTo(i2c_dev_fd, regaddr_MODEL1, new_mode1, I2CWAIT);

    /* set the PRE_SCALE */
    /* using internal oscillator,
     * its freqence refer to pca9685 */
#define IN_OSC_FREQ  (25 * 1000 * 1000)

    /* calculation method of prescalevalue
     * refer to pca9685-manual-7.3.5-(Equation 1) */
    prescalevalue = IN_OSC_FREQ / (4096 * out_freq) - 1;

	ret |= I2CWriteByteTo(i2c_dev_fd, regaddr_PRE_SCALE, prescalevalue, I2CWAIT);

    /* make the PRE_SCALE not set-able */
	ret |= I2CWriteByteTo(i2c_dev_fd, regaddr_MODEL1, old_mode1, I2CWAIT);

    /* ??? */
	usleep(5*1000);
	old_mode1 |= 0x80;
	//set restart bit (causes remembering where was when off)
	ret |= I2CWriteByteTo(i2c_dev_fd, regaddr_MODEL1, old_mode1, I2CWAIT);
	return ret;
}


/* the address calculation algorithm of lednum
 * refer to pca9685-manual-7.3-Register-Definitions
 *
 * decimal address */
#define LedOnRegL(x) (6 +4 * x)
#define LedOnRegH(x) (7 +4 * x)
#define LedOffRegL(x) (8 +4 * x)
#define LedOffRegH(x) (9 +4 * x)

unsigned char pwm_sgrt_setPWMout_on(unsigned char lednum,
        unsigned short onTime){
    if(!succeed_init_flag) {
        fprintf(stderr, DEV_NAME
                "pwm-sgrt does not succeed to init, %s.\n",
                __func__);
        return -1;
    }

    if(lednum > 15 || lednum < 0) {
        fprintf(stderr, DEV_NAME
                "the input-param lednum %d is not valid, %s.\n",
                lednum, __func__);
        return -1;
    }
	unsigned char ret = 0;
	unsigned char onTimeL, onTimeH;
	onTimeL = onTime & 0xff;
	onTimeH = onTime >> 8;
	ret |= I2CWriteByteTo(i2c_dev_fd,  LedOnRegL(lednum), onTimeL, I2CWAIT);
	ret |= I2CWriteByteTo(i2c_dev_fd, LedOnRegH(lednum), onTimeH, I2CWAIT);
    return ret;
}

unsigned char pwm_sgrt_setPWMout_off(unsigned char lednum,
        unsigned short offTime) {
    if(!succeed_init_flag) {
        fprintf(stderr, DEV_NAME
                "pwm-sgrt does not succeed to init, %s.\n",
                __func__);
        return -1;
    }
	unsigned char ret = 0;
	unsigned char offTimeL, offTimeH;
	offTimeL = offTime & 0xff;
	offTimeH = offTime >> 8;
	ret |= I2CWriteByteTo(i2c_dev_fd,  LedOffRegL(lednum), offTimeL, I2CWAIT);
	ret |= I2CWriteByteTo(i2c_dev_fd, LedOffRegH(lednum), offTimeH, I2CWAIT);
}
